#ifndef UNIX
# define GLEW_STATIC 1
#endif

#include <GL/glew.h>

#include "../host.h"
#include "../../log.h"

#define MAX_VERTS 0x10000

#define N_TEXCOORD 2
#define N_VERTCOORD 3
#define N_COLORCOORD 4

#define BUFFER_OFFSET(a) ((char*)NULL + (a))

GLuint drawVbo;

GLenum capabilities[NUM_CAPABILITIES] =
{
    GL_ALPHA_TEST,
    GL_BLEND,
    GL_CULL_FACE,
    GL_DEPTH_TEST,
    GL_TEXTURE_2D,
    GL_DITHER
};

GLenum primitives[] =
{
    GL_POINTS,
    GL_LINES,
    GL_LINE_STRIP,
    GL_TRIANGLES,
    GL_TRIANGLE_STRIP,
    GL_TRIANGLE_FAN,
    GL_QUADS
};

GLenum ztests[] =
{
    GL_NEVER,
    GL_ALWAYS,
    GL_EQUAL,
    GL_NOTEQUAL,
    GL_LESS,
    GL_LEQUAL,
    GL_GREATER,
    GL_GEQUAL
};

GLenum srcFuncs[] =
{
    GL_DST_COLOR,
    GL_ONE_MINUS_DST_COLOR,
    GL_SRC_ALPHA,
    GL_ONE_MINUS_SRC_ALPHA,
    GL_DST_ALPHA,
    GL_ONE_MINUS_DST_ALPHA,
    GL_SRC_ALPHA,
    GL_ONE_MINUS_SRC_ALPHA,
    GL_DST_ALPHA,
    GL_ONE_MINUS_DST_ALPHA,
    GL_CONSTANT_COLOR,
    GL_ONE_MINUS_CONSTANT_COLOR,
    GL_ZERO,
    GL_ONE
};

GLenum dstFuncs[] =
{
    GL_SRC_COLOR,
    GL_ONE_MINUS_SRC_COLOR,
    GL_SRC_ALPHA,
    GL_ONE_MINUS_SRC_ALPHA,
    GL_DST_ALPHA,
    GL_ONE_MINUS_DST_ALPHA,
    GL_SRC_ALPHA,
    GL_ONE_MINUS_SRC_ALPHA,
    GL_DST_ALPHA,
    GL_ONE_MINUS_DST_ALPHA,
    GL_CONSTANT_COLOR,
    GL_ONE_MINUS_CONSTANT_COLOR,
    GL_ZERO,
    GL_ONE
};

GLenum blendFuncs[] =
{
    GL_FUNC_ADD,
    GL_FUNC_SUBTRACT,
    GL_FUNC_REVERSE_SUBTRACT,
    GL_MIN,
    GL_MAX,
    GL_FUNC_ADD
};

GLenum alphaFuncs[] =
{
    GL_NEVER,
    GL_ALWAYS,
    GL_EQUAL,
    GL_NOTEQUAL,
    GL_LESS,
    GL_LEQUAL,
    GL_GREATER,
    GL_GEQUAL
};

void host_geInit()
{
    glGenBuffers(1, &drawVbo);
    glBindBuffer(GL_ARRAY_BUFFER, drawVbo);
    glBufferData(GL_ARRAY_BUFFER,
                 MAX_VERTS * N_VERTCOORD  * sizeof(float)
               + MAX_VERTS * N_TEXCOORD   * sizeof(float)
               + MAX_VERTS * N_COLORCOORD * sizeof(float),
                 NULL,
                 GL_STREAM_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void host_geFree()
{
    glDeleteBuffers(1, &drawVbo);
}

void host_resetMatrices()
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void host_setCapability(RECapability cap, u8 enable)
{
    if (cap >= NUM_CAPABILITIES) {
        _log(ERR, GFX, "Wrong capability!");
        return;
    }
    (enable ? glEnable : glDisable)(capabilities[cap]);
}

void host_setOrthoProj()
{
    glMatrixMode(GL_PROJECTION);
    glOrtho(0, 480, 272, 0, 0, -0xffff);
}

void host_setViewport(u32 x, u32 y, u32 w, u32 h)
{
    glViewport(x, y, w, h);
}

void host_loadProjMatrix(float *m)
{
    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(m);
}

void host_setDepthRange(double znear, double zfar)
{
    glDepthRange(znear, zfar);
}

void host_setShadeModel(GEShadeModel model)
{
    GLenum mdl = GL_FLAT;
    switch (model)
    {
    case GE_SHADE_FLAT:
        mdl = GL_FLAT;
        break;

    case GE_SHADE_SMOOTH:
        mdl = GL_SMOOTH;
        break;
    }

    glShadeModel(mdl);
}

void host_drawPrimitive(float *vertexArr, float *texCoordArr, float *colorArr,
                       GEPrimitiveType prim, u32 numVerts)
{
    u32 vertexSz   = numVerts * N_VERTCOORD  * sizeof(float);
    u32 texCoordSz = numVerts * N_TEXCOORD   * sizeof(float);
    u32 colorSz    = numVerts * N_COLORCOORD * sizeof(float);

    u32 vertexOff   = 0;
    u32 texCoordOff = vertexSz;
    u32 colorOff    = vertexSz + texCoordSz;

    glBindBuffer(GL_ARRAY_BUFFER, drawVbo);
    glBufferSubData(GL_ARRAY_BUFFER,
                    vertexOff,
                    vertexSz,
                    vertexArr);
    glBufferSubData(GL_ARRAY_BUFFER,
                    texCoordOff,
                    texCoordSz,
                    texCoordArr);
    glBufferSubData(GL_ARRAY_BUFFER,
                    colorOff,
                    colorSz,
                    colorArr);

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);

    glVertexPointer(N_VERTCOORD,  GL_FLOAT, 0, BUFFER_OFFSET(vertexOff));
    glTexCoordPointer(N_TEXCOORD, GL_FLOAT, 0, BUFFER_OFFSET(texCoordOff));
    glColorPointer(N_COLORCOORD,  GL_FLOAT, 0, BUFFER_OFFSET(colorOff));

    glDrawArrays(primitives[prim], 0, numVerts);

    glDisableClientState(GL_COLOR_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void host_clear(u8 color, u8 stencil, u8 depth)
{
    GLbitfield mask = 0;
    if (color)
        mask |= GL_COLOR_BUFFER_BIT;
    if (stencil)
        mask |= GL_STENCIL_BUFFER_BIT;
    if (depth)
        mask |= GL_DEPTH_BUFFER_BIT;
    glClear(mask);
}

void host_setMask(u8 color, u8 alpha, u8 depth)
{
    GLboolean colorBool = color ? GL_TRUE : GL_FALSE;
    GLboolean alphaBool = alpha ? GL_TRUE : GL_FALSE;
    GLboolean depthBool = depth ? GL_TRUE : GL_FALSE;
    glColorMask(colorBool, colorBool, colorBool, alphaBool);
    glDepthMask(depthBool);
}

void host_setFrontFace(GEFrontFaceType face)
{
    GLenum frontFace;
    if (face == GE_FRONTFACE_CW)
        frontFace = GL_CW;
    else
        frontFace = GL_CCW;
    glFrontFace(frontFace);
}

void host_setDepthFunc(GEZTest ztst)
{
    GLenum test = ztests[ztst];
    glDepthFunc(test);
}

void host_setBlendEquation(GEBlendFunction func)
{
    GLenum glFunc = blendFuncs[func];
    glBlendEquation(glFunc);
}

void host_setBlendFactors(u8 sfactor, u8 dfactor)
{
    GLenum sFunc = srcFuncs[sfactor];
    GLenum dFunc = dstFuncs[dfactor];
    glBlendFunc(sFunc, dFunc);
}

void host_setBlendColor(float color[4])
{
    glBlendColor(color[0], color[1], color[2], color[3]);
}

void host_setAlphaTest(GEAlphaFunction func, float ref)
{
    GLenum glFunc = alphaFuncs[func];
    glAlphaFunc(glFunc, ref);
}

